﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.ParallelArrays;
using FPA = Microsoft.ParallelArrays.FloatParallelArray;
using BPA = Microsoft.ParallelArrays.BoolParallelArray;
using IPA = Microsoft.ParallelArrays.IntParallelArray;
using PA = Microsoft.ParallelArrays.ParallelArrays;
using System.Diagnostics;

using CSML;

namespace AcceleratorTests
{
   public class TestHandler
    {
        delegate float[,] SimpleDelegate(int times, Stopwatch sw);

        DataHandler datahandler = new DataHandler();

       
        int width;
        int height;

        Random r = new Random();

        float[,] x;
        float[,] y ;
        float[,] z ;

        double[,] xAsDouble;
        double[,] yAsDouble;
        List<List<float>> xAsList;

        float[,] result;
        float[,] result2;

       
        List<int> graphsize = new List<int>();
        List<int> matrixSizes = new List<int>();

        Target evalTarget = new DX9Target();
        FPA test2;
        FPA test1;

        public void t1(int runNo, int size)
        {
            float[,] outArr;
            FPA n1, a ;
            for (int i = 0; i < runNo; i++)
            {
                n1 = new FPA(1, size, size);
                a = PA.Add(1, PA.Add(n1, n1));

                outArr = new float[size, size];
                evalTarget.ToArray(a, out outArr);
            }

        }

        public void t2(int runNo, int size)
        {
            float[,] outArr;
            FPA a;
            for (int i = 0; i < runNo; i++)
            {
                a = PA.Add(1, new FPA(2, size, size));
                outArr = new float[size, size];
                evalTarget.ToArray(a, out outArr);
            }
        }


        public void RunTests()
        {
            int size = 2000;
            int runNo = 200;

            var sw = new System.Diagnostics.Stopwatch();
            FPA a;
            float[,] outArr;


            sw.Reset();
            sw.Start();
            t1(runNo, size);
            sw.Stop();
            System.Console.WriteLine("1+1+1=" + sw.ElapsedTicks);


            sw.Reset();
            sw.Start();
            t2(runNo, size);
            sw.Stop();
            System.Console.WriteLine("1+2=" + sw.ElapsedTicks);

            sw.Reset();
            sw.Start();
            t1(runNo, size);
            sw.Stop();
            System.Console.WriteLine("1+2=" + sw.ElapsedTicks);

            sw.Reset();
            sw.Start();
            t2(runNo, size);
            sw.Stop();
            System.Console.WriteLine("1+2=" + sw.ElapsedTicks);

            Console.ReadKey();

            return;
            

            matrixSizes.Add(2);
            matrixSizes.Add(4);
            matrixSizes.Add(8);
            matrixSizes.Add(16);
            matrixSizes.Add(32);
            matrixSizes.Add(64);
            matrixSizes.Add(64+32);
            
            for(int i = 1;i<30;i++)
                matrixSizes.Add(i*128);
            

            graphsize.Add(1);
            graphsize.Add(2);
            graphsize.Add(25);
            graphsize.Add(50);
            graphsize.Add(100);
            graphsize.Add(200);
            graphsize.Add(300);
            graphsize.Add(400);
            graphsize.Add(500);
            graphsize.Add(600);
            graphsize.Add(700);
            graphsize.Add(800);
            graphsize.Add(900);
            graphsize.Add(1000);

            foreach (int size2 in matrixSizes)
            {
               
                    int w = size2;
                    int h = size2;

                    try
                    {
                        RunTestsOnSize(w, h);
                    }
                    catch (OutOfMemoryException ex)
                    {
                        Console.WriteLine(ex.Message);
                        Console.WriteLine("Test on  size was aborted!");
                        break;
                    }
                    
                    
                
            }
            Console.WriteLine("Tests are done!");
            saveData("tests.csv");
           

        }

        private void saveData(string filename)
        {
            Console.WriteLine("Press ENTER to save result");
            Console.ReadLine();
            bool success = true;
            try
            {
                datahandler.saveToFile(filename);
            }
            catch (Exception ex)
            {
                success = false;
                Console.WriteLine("Data not saved to: " + filename + ", Error: " + ex.Message);
            }
            if (success)
                Console.WriteLine("Data saved to: " + filename);
            else
            {
                 saveData(filename);
                 return;
            }
            Console.ReadLine();
        }

        private void initValues(int w, int h)
        {

            y = new float[width, height];
            z = new float[width, height];
            x = new float[width, height];

            xAsDouble = new double[width, height];
            yAsDouble = new double[width, height];

            result = new float[width, height];
            result2 = new float[width, height];

            xAsList = new List<List<float>>();

            for (int i = 0; i < w; i++)
            {
                xAsList.Add(new List<float>());
                for (int j = 0; j < h; j++)
                {
                    float value = (float)r.NextDouble() * 1000 - 500;
                    x[i, j] = value;
                    xAsList[xAsList.Count - 1].Add(value);
                    xAsDouble[i, j] = (double)value;
                    y[i, j] = (float)r.NextDouble() * 1000 - 500;
                    yAsDouble[i, j] = (double)y[i, j];
                    z[i, j] = (float)r.NextDouble() * 1000 - 500;
                }
            }

        }
        void RunTestsOnSize(int w, int h)
        {


           
          
            width = w;
            height = h;
            Console.WriteLine("== RUNNING TESTS ON " + width + " X " + height + "== " + width * height);
            
            initValues(w, h);
            
            // Transfering
           // datahandler.Logs.Add(TestFunction("Transfer", TransferGPU, TransferCPU, graphsize));
            

            //Simple operations
            //datahandler.Logs.Add(TestFunction("ManyAdd", ManyAddGPU, ManyAddCPU, graphsize));
            //datahandler.Logs.Add(TestFunction("ManyMul", ManyMulGPU, ManyMulCPU, graphsize));
            //datahandler.Logs.Add(TestFunction("ManyDiv", ManyDivGPU, ManyDivCPU, graphsize));
            //datahandler.Logs.Add(TestFunction("ManySub", ManySubGPU, ManySubCPU, graphsize));
            //datahandler.Logs.Add(TestFunction("Add Mul", AddMulGPU, AddMulCPU, graphsize));

            //Graph operations
            //datahandler.Logs.Add(TestFunction("ManyAddReuse", ManyAddReuseGPU, ManyAddReuseCPU, graphsize));
            //datahandler.Logs.Add(TestFunction("ManySubReuse", ManySubReuseGPU, ManySubReuseCPU, graphsize));

            //datahandler.Logs.Add(TestFunction("Simple Graph", simpleGraphGPU, simpleGraphCPU, graphsize));


            //Matrix operations. 
            //datahandler.Logs.Add(TestFunction("MatrixMulti", MatrixMultiplyGPU, MatrixMultiplySimpleCPU, graphsize));
           
       

           
           
            

        }
        DataLog TestFunction(String name, SimpleDelegate GPU, SimpleDelegate CPU, List<int> graphSizes)
        {
            DataLog datalog = new DataLog(name, width * height);

            try
            {
                foreach (int i in graphSizes)
                {

                    datalog.Entries.Add(TimeFunction(name, GPU, CPU, i));

                }
            }
            catch (OutOfMemoryException ex)
            {
                Console.WriteLine("CPU: " + ex.Message);


            }
            catch (PA.AcceleratorException ex)
            {
                Console.WriteLine("GPU: " + ex.Message);
                try
                {
                    evalTarget = new DX9Target();
                }
                catch (PA.AcceleratorException ex2)
                {
                    Console.WriteLine("While fixing: " + ex2.Message);
                    Console.WriteLine("!! Can't create new instance of DX9Target. Might not be able to continue the test and you should probably save it now!!");
                }

                //HACK: Temp fix!
                Console.WriteLine("Do you want to stop now and save results? press 'y'. If not press something else :D");
                char input = Console.ReadKey().KeyChar;
                if (input == 'y' || input == 'Y')
                {
                    Console.WriteLine("Tests are done!");
                    saveData("tests-uncomplete.csv");
                }
            }
            
            return datalog;
        }

       DataLogEntry TimeFunction(String name, SimpleDelegate GPU, SimpleDelegate CPU, int times)
        {
            float[,] GPURes;
            float[,] CPURes;

            TimeSpan CPUtime = new TimeSpan();
            TimeSpan GPUtime = new TimeSpan();
            int numberOfTests = 10;

            List<ResultEntry> resultList = new List<ResultEntry>();
           
            for (int i = 0; i < numberOfTests; i++)
            {
                Stopwatch st = new Stopwatch();
                st.Start();

                GPURes = GPU(times, st);

                if (st.IsRunning)
                    st.Stop();

                GPUtime = GPUtime.Add(st.Elapsed);

                //Console.WriteLine("Elapsed using Accelerator = {00:00:00}", st.Elapsed);

                Stopwatch st2 = new Stopwatch();
                st2.Start();

                CPURes = CPU(times, st2);

                if (st2.IsRunning)
                    st2.Stop();

                CPUtime = CPUtime.Add(st2.Elapsed);

                var re = new ResultEntry(name, CPURes, GPURes);
                re.testResult();

                resultList.Add(re);
            }

           DataLogEntry entry = new DataLogEntry(CPUtime, GPUtime, numberOfTests,times, width * height, resultList);
            Console.WriteLine("Tested " + name + " " + times + " times on size: "+ width*height +". Factor = "+entry.factor);
            return entry;
        }


       float[,] TransferGPU(int times, Stopwatch st)
       {

           FPA b = new FloatParallelArray(x);
           evalTarget.ToArray(b, out result, ExecutionMode.ExecutionModeNormal);
           return result;
       }

       float[,] TransferCPU(int times, Stopwatch st)
       {
           return x;
       }


            
#region Many ADD Graph
         float[,] ManyAddGPU(int times, Stopwatch st)
        {
           
            
            FPA b = new FloatParallelArray(y);
            for (int i = 0; i < times; i++)
            {
                
                b = PA.Add(new FloatParallelArray(x), b);
                
            }

                
            evalTarget.ToArray(b, out result, ExecutionMode.ExecutionModeNormal);
            
            return result;
        }

         float[,] ManyAddCPU(int times, Stopwatch st)
        {
          
            
            
            for (int t = 0; t < times; t++)
            {


                for (int i = 0; i < width; i++)
                {
                    for (int j = 0; j < height; j++)
                    {
                        y[i, j] += x[i, j];
                    }
                }
            }
            return y;
        }

#endregion


         #region Many MUL
         float[,] ManyMulGPU(int times, Stopwatch st)
         {

             FPA a = new FloatParallelArray(x);
             FPA b = new FloatParallelArray(y);
             
             for (int i = 0; i < times; i++)
             {

                 a = PA.Multiply(a, b);

             }


             evalTarget.ToArray(a, out result, ExecutionMode.ExecutionModeNormal);
             return result;
         }

         float[,] ManyMulCPU(int times, Stopwatch st)
         {
             float[,] a = x;
             float[,] b = y;

             for (int t = 0; t < times; t++)
             {


                 for (int i = 0; i < width; i++)
                 {
                     for (int j = 0; j < height; j++)
                     {

                         a[i, j] = a[i, j] * b[i, j];


                     }
                 }
             }
             return a;
         }

         #endregion

         #region Many Sub
         float[,] ManySubGPU(int times, Stopwatch st)
         {
             
             FPA b = new FloatParallelArray(y);

             for (int i = 0; i < times; i++)
             {
                
                 b = PA.Subtract(new FloatParallelArray(x), b);

             }


             evalTarget.ToArray(b, out result, ExecutionMode.ExecutionModeNormal);
             return result;
         }

         float[,] ManySubCPU(int times, Stopwatch st)
         {
             float[,] a = x;
             float[,] b = y;

             for (int t = 0; t < times; t++)
             {


                 for (int i = 0; i < width; i++)
                 {
                     for (int j = 0; j < height; j++)
                     {
                         a = x;
                         b[i, j] = a[i, j] - b[i, j];


                     }
                 }
             }
             return b;
         }

         #endregion
         #region Many Div
         float[,] ManyDivGPU(int times, Stopwatch st)
         {
             FPA a = new FloatParallelArray(x);
             FPA b = new FloatParallelArray(y);


             for (int i = 0; i < times; i++)
             {

                 a = PA.Divide(a, b);

             }


             evalTarget.ToArray(a, out result, ExecutionMode.ExecutionModeNormal);
             return result;
         }

         float[,] ManyDivCPU(int times, Stopwatch st)
         {
             float[,] a = x;
             float[,] b = y;

             for (int t = 0; t < times; t++)
             {


                 for (int i = 0; i < width; i++)
                 {
                     for (int j = 0; j < height; j++)
                     {
                         
                         a[i, j] = a[i, j] / b[i, j];
                     }
                 }
             }
             return a;
         }

         #endregion

         #region Many Add Reuse
         float[,] ManyAddReuseGPU(int times, Stopwatch st)
         {
             FPA a = new FloatParallelArray(x);
             FPA b = new FloatParallelArray(y);

             for (int i = 0; i < times; i++)
             {
                 
                 a = PA.Add(a, b);

             }


             evalTarget.ToArray(a, out result, ExecutionMode.ExecutionModeNormal);
             return result;
         }

         float[,] ManyAddReuseCPU(int times, Stopwatch st)
         {
             float[,] a = x;
             float[,] b = y;

             for (int t = 0; t < times; t++)
             {


                 for (int i = 0; i < width; i++)
                 {
                     for (int j = 0; j < height; j++)
                     {

                         a[i, j] += a[i, j];


                     }
                 }
             }
             return a;
         }

         #endregion

         #region Matrix Multiplication
         float[,] MatrixMultiplyGPU(int times, Stopwatch st)
         {
             FPA r = PA.InnerProduct(new FloatParallelArray(x), new FloatParallelArray(y));

             evalTarget.ToArray(r, out result, ExecutionMode.ExecutionModeNormal);
             return result;
         }
         float[,] MatrixMultiplySimpleCPU(int times, Stopwatch st)
         {

             MatrixMath.Multiply(x, y, result);
             return result;
         }
         float[,] MatrixMultiplyAdvancedCPU(int times, Stopwatch st)
         {
             Matrix m1 = new Matrix(xAsDouble);
             Matrix m2 = new Matrix(yAsDouble);
             
             Matrix r = m1 * m2;

             

             st.Stop();

             float[,] matrixResult = new float[height, width];

             for (int i = 0; i < r.RowCount; i++)
             {
                 for (int j = 0; j < r.ColumnCount; j++)
                 {
                     matrixResult[i, j] = (float)r[i + 1, j + 1].Re;
                 }
             }

             return matrixResult;
         }

         #endregion

         #region Many Sub Reuse
         float[,] ManySubReuseGPU(int times, Stopwatch st)
         {
             FPA a = new FloatParallelArray(x);
             FPA b = new FloatParallelArray(y);

             for (int i = 0; i < times; i++)
             {

                 b = PA.Subtract(a, b);

             }


             evalTarget.ToArray(b, out result, ExecutionMode.ExecutionModeNormal);
             return result;
         }

         float[,] ManySubReuseCPU(int times, Stopwatch st)
         {
             float[,] a = x;
             float[,] b = y;

             for (int t = 0; t < times; t++)
             {


                 for (int i = 0; i < width; i++)
                 {
                     for (int j = 0; j < height; j++)
                     {

                         b[i, j] = a[i, j] - b[i, j];


                     }
                 }
             }
             return b;
         }

         #endregion

         #region Many Add Mul Reuse
         float[,] AddMulGPU(int times, Stopwatch st)
         {
             FPA a = new FloatParallelArray(x);
             FPA b = new FloatParallelArray(y);
             FPA c = new FloatParallelArray(z);
            
             for (int t = 0; t < times; t++)
             {
                
                 a = PA.MultiplyAdd(a, b, c);
                 
             }
             
             evalTarget.ToArray(a, out result, ExecutionMode.ExecutionModeNormal);
             return result;
         }

         float[,] AddMulCPU(int times, Stopwatch st)
         {
             float[,] a = x;
             float[,] b = y;
             float[,] c = z;

            
            
             for (int t = 0; t < times; t++)
             {


                 for (int i = 0; i < width; i++)
                 {
                     for (int j = 0; j < height; j++)
                     {

                         a[i, j] = (x[i, j] * b[i, j]) + c[i, j];
                         

                     }
                 }
                 
             }
             return a;
         }
#endregion

         #region Many Add Mul Reuse
         float[,] simpleGraphGPU(int times, Stopwatch st)
         {
             FPA a = new FloatParallelArray(x);
             FPA b = new FloatParallelArray(y);
             FPA c = new FloatParallelArray(z);


             FPA r = new FloatParallelArray() ;
             for (int t = 0; t < times; t++)
             {
                
                  r = PA.Multiply(PA.Add(a, b), PA.Add(b, c));
                 
             }

             evalTarget.ToArray(r, out result, ExecutionMode.ExecutionModeNormal);
             return result;
         }

         float[,] simpleGraphCPU(int times, Stopwatch st)
         {
             float[,] a = x;
             float[,] b = y;
             float[,] c = z;

             for (int t = 0; t < times; t++)
             {

                 float[,] r = new float[width,height];
                 float[,] r2 = new float[width, height];
                 for (int i = 0; i < width; i++)
                 {
                     for (int j = 0; j < height; j++)
                     {

                        r[i,j] = a[i, j] + b[i, j];
                        r2[i, j] = b[i, j] + c[i, j];
                        a[i, j] = r[i, j] * r2[i, j];

                     }
                 }
             }
             return a;
         }
         #endregion
    }
}
